import java.util.Arrays;
import java.util.LinkedList;

import processing.core.PApplet;


/**
 * a single game of carcarsonne.
 * manages the game logic, game state, etc.
 * 
 * it knows nothing of mouse, sound, etc ... it only deals in game actions
 * 
 * @author ben
 *
 */
public class Game {
	// Game state enumerations
	static public final int STARTING = 0;
	static public final int PLAYING = 1;
	static public final int FINISHING = 2;
	static public final int FINISHED = 3;
	
	// PLAYING substates
	static public final int PLAYING_TILE = 0;
	static public final int PLAYING_MEEPLE = 1;
	static public final int END_TURN = 2;
	
	// link to main controller... 
	private Carcassonne app;
	
	// Game state
	public int state;
	public int playing_state;
	public int whoseTurnIsIt;
	public Player currentPlayer;
	public Tile mNextTile;
	
	// static game elements
	public int numberOfPlayers;
	public Board board;
	public Player players[];
	
	Tile lastTilePlaced;
	
	LinkedList<GlobalFeature> completedGlobalFeatures;	
	
	// e.g., tiles with completed features on them
	LinkedList<Tile> tilesOfInterestLastTurn;
	
	/**
	 * Create a new game
	 */
	public Game(int numberOfPlayers, Carcassonne app)
	{
		this.app = app;
		this.numberOfPlayers = numberOfPlayers;
		
		players = new Player[numberOfPlayers];
		players[0] = new Player("Human", 0, this);
		for(int i=1; i<numberOfPlayers; i++)
		{
			players[i] = new AIPlayer("Robot " + i, i, this);
		}
				
		whoseTurnIsIt = 0; // always player 0's turn first
		currentPlayer = players[whoseTurnIsIt];
		state = STARTING;
		board = new Board(17,17);
		lastTilePlaced = TileFactory.makeTile("city_bit_road");
		board.set(board.width/2,board.height/2,lastTilePlaced);
		
		completedGlobalFeatures = new LinkedList<GlobalFeature>();
		tilesOfInterestLastTurn = new LinkedList<Tile>();
	}
	
	// start the game
	public void start()
	{
		state = PLAYING;
		whoseTurnIsIt = -1;
		endTurn();				
	}
	
	// begin the next turn
	public void beginNextTurn()
	{		
		whoseTurnIsIt = (whoseTurnIsIt + 1)%numberOfPlayers;
		currentPlayer = players[whoseTurnIsIt];
		playing_state = PLAYING_TILE;
		Debug.out.println(currentPlayer.name + "'s turn.");
		if (currentPlayer instanceof AIPlayer)
		{
			// spawn a new thread and call playTurn...
			Thread t = new Thread(new Runnable(){
				public void run()
				{
					try {
						currentPlayer.playTurn(nextTile());
						endTurn();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}				
			});
			t.start();
			return;			
		}		
		else
		{
			app.beginHotseatPlayerTurn();
		}
	}
	
	// end turn
	public void endTurn()
	{
		// check to see if there are any tiles left
		mNextTile = TileFactory.getRandomTile();
		if (mNextTile==null)
		{
			Debug.out.println("Out of tiles!");
			state = FINISHED;
		}
		else
		{		
			beginNextTurn();
		}
	}
		
	public int playingState(){return playing_state;}
	
	// actions
	// state == Playing
	
	/// placeTile, the current player places tile t at position's i, j
	// we assume that the tile is placeable
	public void placeTile(Tile t, int i, int j)
	{
		// board.set(i,j,t);
		t.x = Tile.tw*i;
		t.y = Tile.th*j;
		board.tryTile(i,j,t);
		lastTilePlaced = t;
		if (currentPlayer.hasUnplayedMeeples()) 
			playing_state = PLAYING_MEEPLE;
		else
		{
			playing_state = END_TURN; 
			board.placeTile();
		}
	}
	
	public Tile undoPlaceTile()
	{
		board.undoTile();		
		playing_state = PLAYING_TILE;		
		return lastTilePlaced;
	}
	
	public Tile lastTilePlaced()
	{
		return lastTilePlaced;
	}
	
	/// places a meeple at x,y pixels on the last tile placed
	public void placeMeeple(Tile.MeepleLocation ml)
	{
		assert(canPlaceMeeple(ml));
		
		// confirm the tile placement, then place the meeple and end the turn
		board.placeTile();
		lastTilePlaced.placeMeeple(ml, currentPlayer);
		playing_state = END_TURN;
		calculateTurnScore();
	}
	
	public boolean canPlaceMeeple(Tile.MeepleLocation ml)
	{
		// return !ml.feature.global.isOccupied();
		return board.tryMeepleLocation(ml.feature);
	}
	
	/// skip placing a meeple
	public void skipMeeple()
	{
		playing_state = END_TURN;
		board.placeTile();
		calculateTurnScore();
	}
	
	/// skip playing the tile 
	/// note, this can only be called if there are 
	/// no locations the tile can placed in
	/// the player loses their turn...
	public void skipTile()
	{
		playing_state = END_TURN;
		lastTilePlaced = null;
	}
		
	///
	public void undo()
	{
		
	}
	
	private void calculateTurnScore()
	{
		tilesOfInterestLastTurn.clear();
		
		//  check all global features for completion
		//	find the winning meeple(s)
		//      resolve the score
		//      put this score onto the winning meeple's owners
		//      give all meeples back to the owners (plural)
		
		// copy the list so we can modify the original
		LinkedList<GlobalFeature> gfeatures = new LinkedList<GlobalFeature>(board.globalFeatures);
		
		for(GlobalFeature gf: gfeatures)
		{
			if (gf.isComplete())
			{
				Debug.out.printf("%s completed with %d meeples.\n",Tile.typeToString(gf.type),gf.meeples.size());
				
				completedGlobalFeatures.add(gf);
				board.globalFeatures.remove(gf);
				
				for (Tile.Feature f: gf.features)
					tilesOfInterestLastTurn.add(f.tile);				
				
				// remove meeples / find winner
				if (gf.type==Tile.CITY || gf.type==Tile.ROAD || gf.type==Tile.CLOISTER)
				{
					for (Meeple m: gf.meeples)
					{
						m.owner.score += gf.points;
						m.owner.returnMeeple(m);
						m.tileFeature.meepleLocation.removeMeeple();						
					}
					gf.meeples.clear();
				}
			}						
		}		
		
		// after each placement, print out the global features
		int count[] = new int[4];
		Arrays.fill(count,0);
		for (GlobalFeature gf: board.globalFeatures)
			count[gf.type]++;
		Debug.out.printf("Partial Global Features: %d Farms, %d Cities, %d Roads\n", count[Tile.FARM], count[Tile.CITY], count[Tile.ROAD]);
		for(int i=0; i<numberOfPlayers; i++)
		{
			Debug.out.printf("%s has %d points\n", players[i].name, players[i].score);
		}
		// TODO: calculate scores
		
	}
	
	/// pick the next tile on the board, or null if none left...
	public Tile nextTile()
	{
		return mNextTile;
	}	
}
